////////////////////////////////////////
// EECE478 COMPUTER GRAPHICS
// Spring term 2011
// Assignment 3 City Rendering
// (Tim) Wei-Ting Liu
// #29144052
////////////////////////////////////////

#include <iostream>
#include <time.h>
#include <vector>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <GL/glut.h>
#include "loadCity.h"
#include "loadModel.h"
#include "PPM.hpp"

using namespace std;

static const bool DEBUG = 0;

////////////////////////////////////////////////////////
// Set capture to TURE to capture fames and save to disc
////////////////////////////////////////////////////////
static const bool capture = 0;

/////////////////////////////////////////////////////////////////////
// Counter that controls the camera fly-throught, and the animation
/////////////////////////////////////////////////////////////////////
static float action_clock = 0;

string cityFile;					// City file name
string modelName;					// Current Model file name
char modelPath[80];					// Current Model file path

/////////////////////////
// Animation variables
/////////////////////////
unsigned int numOfModel;						// Number of building model loaded
GLuint modelList;								// Building model displayList
unsigned int* DLList;							// List of of displayLists of all individual building models
GLuint skyBox, shape1, shape2, 
	shape3, shape4, shape5, shape6;
GLuint frame, base1, base2, base3;				// Display lists that contains blocks (multiple buildings), frame of tetris (multiple buildings), and the environment mapping
bool moving = 0 ;								// TRUE if anmination (used for lighting)

clock_t startTime;								// Start time of animation
clock_t tetrisStart;							// Start time of the tetris animation
bool tetris_action = 0;							// TRUE if tetris animation is active
bool showBase = 1;								// TRUE if the base of tetris is showed
vector<unsigned char*> topDownPixelVector;		// Vector that stores the screen captured

static float lightTrig = 0;						// Tracks the number that are used to generate the position of light with trigonometric equations
float lightPos[4] = {0, 0, -10, 1};				// Initial light position
float fogColor[4] = {1.0, 1.0, 1.0, 1.0};		// Color of fog

////////////////////////////////////////////
// Model rendering variables (Assignment 1)
////////////////////////////////////////////
vector<vector<float>> vertexVT;		
vector<vector<float>> normalVT;
vector<string> textureVT;
GLuint* texturePPM;
vector<triangle> triangles;

////////////////////////////////////////////////////////////////////////////
// Variables used to rotate the model and move the camera around the scene
////////////////////////////////////////////////////////////////////////////
// Mouse motion
static int rotateBeginX, rotateBeginY;
static int turnX =0, turnY = 0;
// Keyboard direction key
static float angle = 0.0f;
static float lookX = 0.0f, lookY = 0.0f, lookZ = -1.0f;
static float camX = 0.0f, camY = 0.0f, camZ = 0.0f;

////////////////////////////////////////////////////////////////////////////
// Writes saved screen shot to disc
// This function is called every 200 frames to avoid memory exhaustion
////////////////////////////////////////////////////////////////////////////
void saveScreenShot(){
	static unsigned int fileCount = 1000000;
	char buffer[100];
	cout << "Number of screen shot captured: " << topDownPixelVector.size() << endl;
	cout << "Frame rate: " << topDownPixelVector.size()/5 << " frames/sec" << endl;		// 40 frames per sec, therefore 200 frames captures 5 seconds of animation
	for(int i=0; i<topDownPixelVector.size(); i++){
		string fileDest = "out";		// Output filename: "out"
		fileDest = fileDest + itoa(fileCount, buffer, 10) + ".ppm";
		fileCount++;
		try{
			PPM::Write(fileDest, topDownPixelVector[i], 1280, 720);
			free(topDownPixelVector[i]);
		}catch (string str){
			cout << "Exception from PPM write: " << str << endl;
			exit(1);
		}
	}
	topDownPixelVector.clear();
}

///////////////////////////////////////////////////////
// Capture the current frame and saved it in a vector
///////////////////////////////////////////////////////
void screenShot(){
	GLint viewPort[4];
	glGetIntegerv(GL_VIEWPORT, viewPort);
	int x = viewPort[0];
	int y = viewPort[1];
	int w = 1280; //viewPort[2];
	int h = 720;  //viewPort[3];

	unsigned char *bottomUpPixel = (unsigned char *) malloc( w*h*3*sizeof(unsigned char) );
	unsigned char *topDownPixel = (unsigned char *) malloc( w*h*3*sizeof(unsigned char) );

	glPixelStorei(GL_PACK_ALIGNMENT,1);
	glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, bottomUpPixel);
	// Flip the upside down image
	for(int i=0; i<h; i++)
		memcpy(&topDownPixel[i*w*3], &bottomUpPixel[(h-i-1)*w*3], w*3*sizeof(unsigned char));
	topDownPixelVector.push_back(topDownPixel);
	free(bottomUpPixel);
}

//////////////////////////////////////////////////////////////////////////////////
//  Aquires indies of vectices and normals, and render the model (Assignment 1)
//////////////////////////////////////////////////////////////////////////////////
void drawBuilding(){
	//GLuint texture;

	GLfloat* vertex[3];					// index of vector
	GLfloat* normal;					// index of normal
	unsigned int textureIndex;			// index of texture
	GLfloat* textureCoordinate[3];		// index of texture corrdinate

	glEnable(GL_TEXTURE_2D);
	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	
	for(int renderTriangle=0; renderTriangle<triangles.size(); renderTriangle++){

		// Put vertex, normal, texture coordinates into array format, was vector
		for(int i=0; i<3; i++){
			vertex[i] = &vertexVT[triangles[renderTriangle].vertex[i]][0];
			textureCoordinate[i] = &triangles[renderTriangle].textureCoordinate[i][0];
		}
		normal = &normalVT[triangles[renderTriangle].normal][0];
		textureIndex = triangles[renderTriangle].texture;
		glBindTexture(GL_TEXTURE_2D, texturePPM[textureIndex]);

		glPushMatrix();
		glEnable(GL_CULL_FACE);
		glBegin(GL_TRIANGLES);
			glNormal3fv(normal);
			glTexCoord2fv(textureCoordinate[0]); glVertex3fv(vertex[0]);
			glTexCoord2fv(textureCoordinate[1]); glVertex3fv(vertex[1]);
			glTexCoord2fv(textureCoordinate[2]); glVertex3fv(vertex[2]);
		glEnd();
		glEnable(GL_CULL_FACE);
		glPopMatrix();
		//glDeleteTextures(1, &texturePPM[textureIndex]);
	}
	glDisable(GL_TEXTURE_2D);
}

void reshape(int w, int h){
	// Set screen to 1280x720 when capture mode is TURE
	if(capture){
		w = 1280;
		h = 720;
	}
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(40.0, (GLfloat) w / (GLfloat) h, 1.0, 1000.0);
	
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
}

////////////////////////////////////////////
// Displays my name and name of the model
////////////////////////////////////////////
void displayName(){
    glPushMatrix();
    glLoadIdentity();
	int glState;
	glGetIntegerv(GL_LIGHTING, &glState);
	if(glState == 1){
		glDisable(GL_LIGHTING);
		glDisable(GL_LIGHT0);
	}
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    gluOrtho2D(0, 400, 0, 300);

	void* font = GLUT_BITMAP_9_BY_15;
    float color[4] = {1, 1, 1, 1};
	glRasterPos2f(10, 280);
	string s = cityFile;
	for (int i=0; i<s.size(); i++) {
		glutBitmapCharacter(font, s[i]);
	}
	//glRasterPos2f(280, 10);
	//s = "#29144052";
	//for (int i=0; i<s.size(); i++) {
	//	glutBitmapCharacter(font, s[i]);
	//}
	glRasterPos2f(280, 25);
	s = "Tim Liu";
	for (int i=0; i<s.size(); i++) {
		glutBitmapCharacter(font, s[i]);
	}
    glPopMatrix();
	if(glState == 1){
		glEnable(GL_LIGHTING);
		glEnable(GL_LIGHT0);
	}
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
}

///////////////////////////////////////////////////////////////////////
// Rotate and translate the buildings (displaylist of buildings)
// according to the action_clock
///////////////////////////////////////////////////////////////////////
void tetris(){
	// First falling block: shape 6 travels 11 blocks to reach destination; so 11 seconds required (1 second to travle 1 block)
	// (The rest of the function follows the same idea)
	if(action_clock < tetrisStart+11*CLOCKS_PER_SEC && action_clock != 0){
		glPushMatrix();
		glTranslatef(140, 0, -140);
		glRotatef(-90, 0,1,0);
		glCallList(shape3);
		glPopMatrix();

		static int shape6_x = 0;
		if(tetrisStart+2*CLOCKS_PER_SEC < action_clock && shape6_x == 0) shape6_x-=10;
		glPushMatrix();
		// Block starts from z=-15, and z-- per second
		// this block stops at z=-4, so (15-4=) 11 seconds required
		int tmp = (-10*((15*CLOCKS_PER_SEC-(action_clock-tetrisStart))/1000))/10;
		glTranslatef(50+shape6_x, 0, tmp*10);
		glCallList(shape6);
		glPopMatrix();
		glutPostRedisplay();
	}
	if(tetrisStart+11*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+21*CLOCKS_PER_SEC && action_clock != 0){
		glPushMatrix();
		glTranslatef(150, 0, -130);
		glRotatef(90, 0,1,0);
		glCallList(shape1);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(40, 0, -40);
		glCallList(shape6);
		glPopMatrix();

		static int shape3_x = 0;
		static int shape3_rotate = 0;
		if(tetrisStart+13*CLOCKS_PER_SEC < clock() && shape3_x == 0) shape3_x+=10;
		if(tetrisStart+14*CLOCKS_PER_SEC < clock() && shape3_rotate == 0) shape3_rotate = -90;
		if(tetrisStart+16*CLOCKS_PER_SEC < clock() && shape3_x == 10) shape3_x+=10;
		glPushMatrix();
		int tmp = (-10*((15*CLOCKS_PER_SEC-(action_clock-(tetrisStart+11*CLOCKS_PER_SEC)))/1000) )/ 10;
		glTranslatef(shape3_x+50, 0, tmp*10);
		glRotatef(shape3_rotate, 0,1,0);
		glCallList(shape3);
		glPopMatrix();
		glutPostRedisplay();
	}
	if(tetrisStart+21*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+26*CLOCKS_PER_SEC && action_clock != 0){
		glPushMatrix();
		glTranslatef(140, 0, -140);
		glRotatef(90, 0,1,0);
		glCallList(shape5);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(40, 0, -40);
		glCallList(shape6);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(70, 0, -50);
		glRotatef(-90, 0,1,0);
		glCallList(shape3);
		glPopMatrix();

		static int shape1_x = 0;
		static int shape1_rotate = 90;
		if(tetrisStart+23*CLOCKS_PER_SEC < action_clock && shape1_rotate == 90) shape1_rotate = 0;
		if(tetrisStart+23.5*CLOCKS_PER_SEC < action_clock && shape1_x == 0) shape1_x+=10;
		if(tetrisStart+24*CLOCKS_PER_SEC < action_clock && shape1_x == 10) shape1_x+=10;
		if(tetrisStart+25*CLOCKS_PER_SEC < action_clock && shape1_x == 20) shape1_x+=10;
		if(tetrisStart+25.5*CLOCKS_PER_SEC < action_clock && shape1_x == 30) shape1_x+=10;
		
		glPushMatrix();
		int tmp = (-10*((15*CLOCKS_PER_SEC-(action_clock-(tetrisStart+21*CLOCKS_PER_SEC)))/1000)) / 10;
		glTranslatef(shape1_x+60, 0, tmp*10);
		glRotatef(shape1_rotate, 0,1,0);
		glCallList(shape1);
		glPopMatrix();
		glutPostRedisplay();
	}
	if(tetrisStart+26*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+27.5*CLOCKS_PER_SEC && clock != 0){
		glPushMatrix();
		glTranslatef(140, 0, -140);
		glRotatef(90, 0,1,0);
		glCallList(shape5);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(40, 0, -40);
		glCallList(shape6);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(70, 0, -50);
		glRotatef(-90, 0,1,0);
		glCallList(shape3);
		glPopMatrix();

		glPushMatrix();
		float tmp = (tetrisStart+28*CLOCKS_PER_SEC)-action_clock;
		tmp = 5*tmp/100;
		glTranslatef(100, 0, -tmp);
		glRotatef(0, 0,1,0);
		glCallList(shape1);
		glPopMatrix();
		glutPostRedisplay();
	}
	if(tetrisStart+27.5*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+29*CLOCKS_PER_SEC && clock != 0){
		glPushMatrix();
		glCallList(base3);
		glPopMatrix();
	}
	if(tetrisStart+27.5*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+28*CLOCKS_PER_SEC && clock != 0){
		glPushMatrix();
		glCallList(base2);
		glPopMatrix();
	}
	if(tetrisStart+28.5*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+29*CLOCKS_PER_SEC && clock != 0){
		glPushMatrix();
		glCallList(base2);
		glPopMatrix();
	}
	if(tetrisStart+27.5*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+29*CLOCKS_PER_SEC && clock != 0){
		glPushMatrix();
		glTranslatef(140, 0, -140);
		glRotatef(90, 0,1,0);
		glCallList(shape5);
		glPopMatrix();
	}
	if(tetrisStart+29.5*CLOCKS_PER_SEC < action_clock && clock != 0){
		glPushMatrix();
		glTranslatef(0, 0, 30);
		glCallList(base3);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(140, 0, -140);
		glRotatef(90, 0,1,0);
		glCallList(shape5);
		glPopMatrix();
	}
	if(tetrisStart+0*CLOCKS_PER_SEC < action_clock && action_clock < tetrisStart+27.5*CLOCKS_PER_SEC && clock != 0){
		glPushMatrix();
		glCallList(base1);
		glPopMatrix();
	}

}

//////////////////////////////////////////////////////////////////
// Render the base of tetris from the start
// This function is called every frame until the base disappares
//////////////////////////////////////////////////////////////////
void showBase1(){
		glPushMatrix();
		glCallList(base1);
		glPopMatrix();
}


void display(void){
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glPushMatrix();

	float KaX = lightPos[1]/50;
	GLfloat lightDay[] = {(1+KaX)*(2.0), (1+KaX)*(1+KaX), (1+KaX)*abs((KaX*2)), 1.0f};
	glLightfv(GL_LIGHT0, GL_AMBIENT, lightDay);
	glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
	
	glRotatef(turnY, 1.0, 0.0, 0.0);
	glRotatef(turnX, 0.0, 1.0, 0.0); 
	
	// If tetris_action is TURE, call tetris() to animate buildings
	if(tetris_action){
		tetris();
	}
	/////////////////////////////////////////////////////////////////////
	// Uncomment next 2 lines to generate city according to city file
	/////////////////////////////////////////////////////////////////////
	//glListBase(modelList);
	//	glCallLists(numOfModel, GL_UNSIGNED_INT, DLList);

	////////////////////////////////////////////////////////////////
	// Comment next 3 lines to NOT generate tetris frame and base
	////////////////////////////////////////////////////////////////
	if(showBase) showBase1();
	glCallList(frame);
	glCallList(skyBox);

	displayName();
	glPopMatrix();	

	//////////////////////////////////////////////////////
	// Uncomment to render a sphere at location of light
	//////////////////////////////////////////////////////
	//glPushMatrix();
	//int tmp_stat=0;
	//glGetIntegerv(GL_LIGHTING, &tmp_stat);
	//if(tmp_stat == 1){
	//	glDisable(GL_LIGHTING);
	//	glDisable(GL_LIGHT0);
	//}
	//glTranslatef(lightPos[0], lightPos[1], lightPos[2]);
	//glutSolidSphere(1, 20, 20);
	//if(tmp_stat == 1){
	//	glEnable(GL_LIGHTING);
	//	glEnable(GL_LIGHT0);
	//}
	//glPopMatrix();
	//////////////////////////////////////

	glutSwapBuffers();
}

static float idleStart = 0;

////////////////////////////////////////////////////////////////////////////////////////////////
// OpenGL idle function, called when no comment inputed
// idle is used to animate the fly-throught of the city
// 'action_clock'counts the time of idle
// according to the 'action_clock', movment of the camera and tetris blocks are determined
////////////////////////////////////////////////////////////////////////////////////////////////
void idle(void){

	// if capture is TRUE, action_clock is a counter increment by 25 (thus capturing 40 frames per second)
	// else, action_clock is system clock
	if(capture){	
		action_clock += 25;
		startTime = 0;
	}else{
		action_clock = clock();
	}

	// If cature is FALSE, animation is according to system clock
	// therefore update screen 60 times per second (60 frames/sec)
	if(action_clock-idleStart > CLOCKS_PER_SEC/60 || idleStart == 0 || capture){
		idleStart = action_clock;
		float tmp;

		////////////////////////////////////////////////////////////
		// Position of the light is moved around a curved line
		// curved line is determined using trig with sin and cos
		////////////////////////////////////////////////////////////
		if(lightTrig > 6.283)
			lightTrig = 0;
		lightTrig += 0.0017;
		lightPos[0] = 100+150*cos(lightTrig);
		lightPos[1] = 150*sin(lightTrig);
		
		// From 0th second to 5th second, move camera's X and Z while keep looking at the same point
		// (The rest of the function follows the similar idea)
		if(action_clock < startTime+5*CLOCKS_PER_SEC && action_clock != 0){
			glLoadIdentity();
			tmp = (action_clock- startTime);
			camX = 190 - tmp/100;
			tmp = (action_clock - startTime);
			camY = tmp/1000;
			camZ = -15;
			gluLookAt(camX,camY,camZ, 140,0,-30, 0.0f,1.0f,0.0f);
		}
		// Second 5 to 20
		// Rotate camera around a fixed point
		if(action_clock > startTime+5*CLOCKS_PER_SEC && action_clock < startTime+20*CLOCKS_PER_SEC && action_clock != 0){
			float camXX, camZZ;
			static float camTrig;
			camTrig = ((6.283/(15/3))*(action_clock-(startTime+5*CLOCKS_PER_SEC))/1000)+(1.571);
			glLoadIdentity();
			camXX = 140 + 15*cos(camTrig);
			camZZ = -30 + 15*sin(camTrig);
			gluLookAt(camXX,camY,camZZ, 140,0,-30, 0.0f,1.0f,0.0f);
			if(action_clock > startTime+11*CLOCKS_PER_SEC) glTranslatef(0,0,-10);
			if(action_clock > startTime+15*CLOCKS_PER_SEC) glTranslatef(10,0,0);
		}
		// Second 10 to 11
		if(action_clock > startTime+10*CLOCKS_PER_SEC && action_clock < startTime+11*CLOCKS_PER_SEC && action_clock != 0){	
			tmp = (startTime+10*CLOCKS_PER_SEC)-action_clock;
			glTranslatef(0,0,tmp/100);
		}
		// Second 14 to 15
		if(action_clock > startTime+14*CLOCKS_PER_SEC && action_clock < startTime+15*CLOCKS_PER_SEC && action_clock != 0){
			static bool t_flag = 0;
			//Start tetris animation
			if(!t_flag){
				tetris_action = t_flag= 1;
				tetrisStart = action_clock;
				showBase = 0;
			}
			tmp = -((startTime+14*CLOCKS_PER_SEC)-action_clock);
			glTranslatef(tmp/100,0,0);
		}
		// Second 19 to 20
		if(action_clock > startTime+19*CLOCKS_PER_SEC &&action_clock < startTime+20*CLOCKS_PER_SEC && action_clock != 0){
			tmp = -((startTime+19*CLOCKS_PER_SEC)-action_clock);
			glTranslatef(-tmp/100,0,tmp/100);
		}
		// Second 20 to 25
		if(action_clock > startTime+20*CLOCKS_PER_SEC && action_clock < startTime+25*CLOCKS_PER_SEC && action_clock != 0){
			glLoadIdentity();
			tmp = (action_clock - (startTime+20*CLOCKS_PER_SEC));
			camX = 140 - tmp/100;
			tmp = (action_clock - (startTime+20*CLOCKS_PER_SEC));
			camZ = -15;
			float camXX, camZZ;
			static float camTrig;
			camTrig = ((6.283/(5*4))*(action_clock-(startTime+20*CLOCKS_PER_SEC))/1000)-(1.571);
			camXX = camX + -15*cos(camTrig);
			camZZ = camZ + 15*sin(camTrig);
			gluLookAt(camX,5,camZ, camXX,0,camZZ, 0.0f,1.0f,0.0f);
		}
		// Second 25 to 30
		if(action_clock > startTime+25*CLOCKS_PER_SEC && action_clock < startTime+30*CLOCKS_PER_SEC && action_clock != 0){
			// Disable fog
			if(action_clock > startTime+25.5*CLOCKS_PER_SEC){
				int fogState;
				glGetIntegerv(GL_FOG, &fogState);
				if(fogState == 1)
					glDisable(GL_FOG);	
			}
			glLoadIdentity();
			tmp = action_clock - (startTime+25*CLOCKS_PER_SEC);
			camY = 5 + tmp/1000;

			float camXX, camZZ;
			static float camTrig; // = 1.57;
			camTrig = ((6.283/(5*1.4)*(action_clock-(startTime+25*CLOCKS_PER_SEC))/1000)-(0));
			camXX = camX + -15*cos(camTrig);
			camZZ = camZ + 15*sin(camTrig);
			gluLookAt(camX,camY,camZ, camXX,0,camZZ, 0.0f,1.0f,0.0f);
		}
		// Second 30 to 35
		if(action_clock > startTime+30*CLOCKS_PER_SEC && action_clock < startTime+35*CLOCKS_PER_SEC && action_clock != 0){
			glLoadIdentity();
			tmp = action_clock - (startTime+30*CLOCKS_PER_SEC);
			static float cam_z = camZ;
			float camZZ;
			camZ = cam_z + tmp/50;
			camY = 5 + tmp/20;
			camZZ = -30 - tmp/75;
			gluLookAt(camX,camY,camZ, 90,0,camZZ, 0.0f,1.0f,0.0f);
		}

		// Capture screen until second 62 (end of camera animation and tetris animation
		if(action_clock < startTime+ 62*CLOCKS_PER_SEC && action_clock != 0){
			if(capture){
				screenShot();
			}
			// Write screen shots to disc every 5 second (200 frames)
			if(capture && (int)action_clock % 5000 == 0){
				cout << "Played time: " << action_clock/1000 << endl;
				saveScreenShot();
			}
		}

	glutPostRedisplay();

	}

}

// Tracks mouse's motion, and calculate the rotation required by the model: (turnX, turnY)
// (Assignment 1)
void motion(int x, int y){
	turnX = turnX + (x - rotateBeginX);
	turnY = turnY + (y - rotateBeginY);
	if(DEBUG)
		cout << "X: " << turnX << "\t Y: "<< turnY << endl;
	rotateBeginX = x;
	rotateBeginY = y;
	glutPostRedisplay();
}

void mouse(int button, int state, int x, int y){
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		rotateBeginX = x;
		rotateBeginY = y;
	}
}

// Move the camera's position along the X-Z plane according to camX and camZ
// (Assignment 1)
void zoom(int i){
	camX = camX + i*(lookX)*0.1;
	camZ = camZ + i*(lookZ)*0.1;
	glLoadIdentity();
	if(DEBUG)
		cout << "\tcam: [" << camX << " | " << camY << " | " << camZ << "]\t[" << lookX << " | " << lookY << " | " << lookZ << "]" << endl;
	gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
	glutPostRedisplay();
}

// Rotate the camera
// (Assignment 1)
void turnCam(float ang) {
	lookX = sin(ang);
	lookZ = -cos(ang);

	glLoadIdentity();
	if(DEBUG)
		cout << "\tcam: [" << camX << " | " << camY << " | " << camZ << "]\t[" << lookX << " | " << lookY << " | " << lookZ << "]" << endl;

	gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
	glutPostRedisplay();
}

// Changes the camera's position and target using zoom() and turnCam()
// (Assignment 1)
void inputKey(int key, int x, int y){
	switch(key){
	case GLUT_KEY_UP: 	if(DEBUG) cout<<"^\n"; zoom(5); break;
	case GLUT_KEY_DOWN: 	if(DEBUG) cout<<"v\n"; zoom(-5); break;
	case GLUT_KEY_LEFT: 	if(DEBUG) cout<<"<\n"; angle -= 0.01f; turnCam(angle); break;
	case GLUT_KEY_RIGHT: 	if(DEBUG) cout<<">\n"; angle += 0.01f; turnCam(angle); break;
	}
}

void keyboard(unsigned char key, int x, int y){
	int glState = 0;


	switch (key) {
	case 't':
	case 'T':
		if(!tetris_action){	tetris_action = 1;
		}else{	tetris_action = 0;}
		tetrisStart = clock();
		cout << "t: " << tetris_action << endl;
		glutPostRedisplay();
		break;

	case 'l':
	case 'L':
		// Enable or disable lighting
		glGetIntegerv(GL_LIGHTING, &glState);
		if(glState == 1){
			glDisable(GL_LIGHTING);
			glDisable(GL_LIGHT0);
		}else{
			glEnable(GL_LIGHTING);
			glEnable(GL_LIGHT0);
		}
		glutPostRedisplay();
		break;

	case 'f':
	case 'F':
		// Enable or disable lighting
		glGetIntegerv(GL_FOG, &glState);
		if(glState == 1){
			glDisable(GL_FOG);
		}else{
			glEnable(GL_FOG);
		}
		glutPostRedisplay();
		break;

	case 'r':
	case 'R': 
		// Reset view
		angle = lightTrig = 0;
		lookX = lookY = 0.0f; lookZ = -1.0f;
		turnX = turnY = 0;
		camX = 0.0f; camY = 0.0f; camZ = 10.0f;
		glLoadIdentity();
		gluLookAt(camX,camY,camZ, camX+lookX,lookY,camZ+lookZ, 0.0f,1.0f,0.0f);
		glutPostRedisplay();
		break;

	case 'a':
	case 'A':
		// Start/terminates animation
		startTime = clock();
		action_clock = 0;
		if(!moving){
			glutIdleFunc(idle);
			moving = 1;
			break;
		}else{
			glutIdleFunc(NULL);
			moving = 0;
			break;
		}

	case 27: exit(0); break;
	default: break;
    }
}

// Convert PPM file using PPM class provided
// (Assignment1)
void loadTexture(vector<string> textureVT){
	texturePPM = new GLuint[textureVT.size()];
	
	int textureWidth;
	int textureHeight;

	for(int i=0; i<textureVT.size(); i++){
		unsigned char* tr;
		try{
			char path[80];
			strcpy(path, modelPath);
			strcat(path, textureVT[i].c_str());
			tr = PPM::Read(path, textureWidth, textureHeight);
		}catch (string str){
			cout << "Exception from PPM read: " << str << endl;
			exit(1);
		}
		glGenTextures(1, &texturePPM[i]);
		glBindTexture(GL_TEXTURE_2D, texturePPM[i]);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, textureHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, tr);

		delete tr;
	}
}

// Creates loadModel object, which parses the input data
// Stores model's vectices and normals into arrays
// (Assignment1)
void loadFile(){
	loadModel model(modelName);

	vertexVT = model.getVertex();
	normalVT = model.getNormal();
	textureVT = model.getTexture();
	loadTexture(textureVT);
	triangles = model.getTriangle();
}

//////////////////////////////////////////////////
// Generate display list for each building model
//////////////////////////////////////////////////
void genModelDL(int index, vector<float> scalingFactor){

	cout << index << ": ";
	for(int i=0; i<scalingFactor.size(); i++){
		cout << scalingFactor[i] << " ";
	}
	cout << endl;

	glNewList(modelList+index, GL_COMPILE);
		glPushMatrix();

		glTranslatef(scalingFactor[9], scalingFactor[10], scalingFactor[11]);
        glScalef(scalingFactor[6], scalingFactor[7], scalingFactor[8]);
        glRotatef(scalingFactor[5], 0, 0, 1);
        glRotatef(scalingFactor[4], 0, 1, 0);
        glRotatef(scalingFactor[3], 1, 0, 0);
        glTranslatef(scalingFactor[0]*-1, scalingFactor[1]*-1, scalingFactor[2]*-1);
		drawBuilding();

		glPopMatrix();
	glEndList();
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////
// Generate display list of tetris blocks and tetris frames using display list of buildings generated
/////////////////////////////////////////////////////////////////////////////////////////////////////////
void displayList(){
	unsigned int toDraw[1];
	toDraw[0] = 1;

	// DisplayList: Frame
	frame = glGenLists(1);
	glNewList(frame, GL_COMPILE);
		glListBase(modelList);
		for(int i=0; i<=160; i+=10){
			glPushMatrix();
			glTranslatef(110, 0, -i);
			glCallLists(1, GL_UNSIGNED_INT, toDraw);
			glPopMatrix();
			glPushMatrix();
			glTranslatef(0, 0, -i);
			glCallLists(1, GL_UNSIGNED_INT, toDraw);
			glPopMatrix();
			glPushMatrix();
			glTranslatef(180, 0, -i);
			glCallLists(1, GL_UNSIGNED_INT, toDraw);
			glPopMatrix();
			if(i<120){
				for(int j=120; j<= 170; j+=10){
					if(!((i == 20 || i == 30))){
						glPushMatrix();
						glTranslatef(j, 0, -i);
						glCallLists(1, GL_UNSIGNED_INT, toDraw);
						glPopMatrix();
					}
				}
			}
		}
		for(int i=0; i<=180; i+=10){
			glPushMatrix();
			glTranslatef(i, 0, 0);
			glCallLists(1, GL_UNSIGNED_INT, toDraw);
			glPopMatrix();
			glPushMatrix();
			glTranslatef(i, 0, -160);
			glCallLists(1, GL_UNSIGNED_INT, toDraw);
			glPopMatrix();

		}
		glPushMatrix();
		toDraw[0] = 8;
		glTranslatef(120, 0, -30);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 9;
		glTranslatef(120, 0, -20);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 10;
		glTranslatef(130, 0, -30);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 11;
		glTranslatef(130, 0, -20);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 12;
		glTranslatef(140, 0, -30);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 13;
		glTranslatef(140, 0, -20);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 14;
		glTranslatef(150, 0, -30);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 15;
		glTranslatef(150, 0, -20);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		toDraw[0] = 16;
		glTranslatef(160, 0, -30);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(160, 0, -20);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(170, 0, -30);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
		glPushMatrix();
		glTranslatef(170, 0, -20);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();

	glEndList();

	// DisplayList: skyBox
	toDraw[0] = 0;
	skyBox = glGenLists(1);
	glNewList(skyBox, GL_COMPILE);
	glListBase(modelList);
	glTranslatef(90, 0, -90);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glEndList();

	// DisplayList: Shape 1
	toDraw[0] = 2;
	shape1 = glGenLists(1);
	glNewList(shape1, GL_COMPILE);
	glListBase(modelList);
	for(int i=-10; i<=20; i+=10){
		glPushMatrix();
		glTranslatef(0, 0, -i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
	}
	glEndList();

	// DisplayList: Shape 2
	toDraw[0] = 3;
	shape2 = glGenLists(1);
	glNewList(shape2, GL_COMPILE);
	glListBase(modelList);

	for(int i=-10; i<=0; i+=10){
		glPushMatrix();
		glTranslatef(0, 0, -i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();

		glPushMatrix();
		glTranslatef(10, 0, -10-i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
	}
	glEndList();

	// DisplayList: Shape 3
	toDraw[0] = 4;
	shape3 = glGenLists(1);
	glNewList(shape3, GL_COMPILE);
	glListBase(modelList);
	for(int i=-10; i<=0; i+=10){
		glPushMatrix();
		glTranslatef(10, 0, -i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();

		glPushMatrix();
		glTranslatef(0, 0, -10-i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
	}
	glEndList();

	// DisplayList: Shape 4
	toDraw[0] = 5;
	shape4 = glGenLists(1);
	glNewList(shape4, GL_COMPILE);
	glListBase(modelList);

	for(int i=-10; i<=10; i+=10){
		glPushMatrix();
		glTranslatef(0, 0, -i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
	}
	glPushMatrix();
	glTranslatef(10, 0, 10);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glEndList();

	// DisplayList: Shape 5
	toDraw[0] = 6;
	shape5 = glGenLists(1);
	glNewList(shape5, GL_COMPILE);
	glListBase(modelList);

	for(int i=-10; i<=10; i+=10){
		glPushMatrix();
		glTranslatef(0, 0, -i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
	}
	glPushMatrix();
	glTranslatef(-10, 0, 10);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glEndList();

	// DisplayList: Shape 6
	toDraw[0] = 7;
	shape6 = glGenLists(1);
	glNewList(shape6, GL_COMPILE);
	glListBase(modelList);

	for(int i=-10; i<=10; i+=10){
		glPushMatrix();
		glTranslatef(0, 0, -i);
		glCallLists(1, GL_UNSIGNED_INT, toDraw);
		glPopMatrix();
	}
	glPushMatrix();
	glTranslatef(10, 0, 0);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glEndList();

	// DisplayList: base1
	base1 = glGenLists(1);
	glNewList(base1, GL_COMPILE);
	glPushMatrix();
	glTranslatef(10, 0, -20);
	glCallList(shape4);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(20, 0, -30);
	glCallList(shape2);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(40, 0, -10);
	glRotatef(-90, 0,1,0);
	glCallList(shape1);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(40, 0, -20);
	glRotatef(-90, 0,1,0);
	glCallList(shape1);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(80, 0, -10);
	glRotatef(90, 0,1,0);
	glCallList(shape6);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(60, 0, -30);
	glRotatef(90, 0,1,0);
	glCallList(shape5);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(80, 0, -30);
	glCallList(shape3);
	glPopMatrix();
	glEndList();

	// DisplayList: base2
	base2 = glGenLists(1);
	glNewList(base2, GL_COMPILE);
	glPushMatrix();
	glTranslatef(10, 0, -20);
	glCallList(shape4);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(40, 0, -10);
	glRotatef(-90, 0,1,0);
	glCallList(shape1);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(40, 0, -20);
	glRotatef(-90, 0,1,0);
	glCallList(shape1);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(60, 0,-30);
	glRotatef(90, 0,1,0);
	glCallList(shape5);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(80, 0, -10);
	glRotatef(90, 0,1,0);
	glCallList(shape6);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 3;
	glTranslatef(20, 0, -20);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(20, 0, -30);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(30, 0, -30);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 7;
	glTranslatef(40, 0, -30);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 4;
	glTranslatef(90, 0, -20);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(90, 0, -30);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(80, 0, -30);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 2;
	glTranslatef(100, 0, -10);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(100, 0, -20);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(100, 0, -30);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glEndList();
	// DisplayList: base3
	base3 = glGenLists(1);
	glNewList(base3, GL_COMPILE);
	glPushMatrix();
	glTranslatef(70, 0, -50);
	glRotatef(-90, 0,1,0);
	glCallList(shape3);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 3;
	glTranslatef(30, 0, -40);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 7;
	glTranslatef(50, 0, -40);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(40, 0, -40);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	glTranslatef(40, 0, -50);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 4;
	glTranslatef(80, 0, -40);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glPushMatrix();
	toDraw[0] = 2;
	glTranslatef(100, 0, -40);
	glCallLists(1, GL_UNSIGNED_INT, toDraw);
	glPopMatrix();
	glEndList();
}

//////////////////////////////////////////////////
// Reads the city files and go through each model
// by calling genModelDL()
//////////////////////////////////////////////////
void loadData(){
	loadCity city(cityFile);
	numOfModel = city.getNumOfModel();
	DLList = new unsigned int[numOfModel];
	modelList = glGenLists(city.getNumOfModel());

	for(int i=0; i<city.getNumOfModel(); i++){
		DLList[i] = i;
		modelName = city.getModelName()[i];
		string name = modelName;
		int index = -1;
		for(int i=0; i<name.size(); i++){
			if(name[i] == '/' || name[i] == '\\'){
				index = i;
			}
		}
		unsigned int length = name.copy(modelPath, index+1, 0);
		modelPath[length] = '\0';
		
		loadFile();
		genModelDL(i, city.getScalingFactor()[i]);
	}
	displayList();
}

void init(void){
	glShadeModel(GL_SMOOTH);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glEnable(GL_DEPTH_TEST);
	glFogi (GL_FOG_MODE, GL_EXP2);
	glFogfv (GL_FOG_COLOR, fogColor);
	glFogf (GL_FOG_DENSITY, 0.01);
	glHint (GL_FOG_HINT, GL_DONT_CARE);
	glFogf(GL_FOG_START, 1.0);
	glFogf(GL_FOG_END, 5.0);
    GLfloat lightKd[] = {.7f, .7f, .7f, 1.0f};  // diffuse light
    GLfloat lightKs[] = {1, 1, 1, 1};           // specular light
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightKd);
    glLightfv(GL_LIGHT0, GL_SPECULAR, lightKs);

	loadData();
}

int main(int argc, char **argv){
	if(argc != 2){
		cout << "Usage: renderCity.exe [City File Name]\nex. renderCity.exe myCity.city" << endl;
		//system("PAUSE");
		return 0;
	}else{
		cityFile = argv[1];
	}

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_DEPTH);
	glutCreateWindow("EECE478 Assignment 3: City");

	glutReshapeFunc(reshape);
	glutDisplayFunc(display);

	glutKeyboardFunc (keyboard);
	glutSpecialFunc(inputKey);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);

	init();
	glutMainLoop();
}